{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit DesignerList;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ImgList, PngImageList, ComCtrls, ExtCtrls, StdCtrls, UIObjects,
  ActnList;

type
  TfrmObjectList = class(TForm)
    pbHeader: TPaintBox;
    lbCaption: TLabel;
    lbAction: TLabel;
    btClose: TButton;
    ActionList1: TActionList;
    acAdd: TAction;
    acRemove: TAction;
    acMoveUp: TAction;
    acMoveDown: TAction;
    pnListItems: TPanel;
    Bevel1: TBevel;
    Bevel3: TBevel;
    tcTabs: TTabControl;
    lvItems: TListView;
    btAdd: TButton;
    btRemove: TButton;
    btMoveUp: TButton;
    btMoveDown: TButton;
    btEdit: TButton;
    acEdit: TAction;
    procedure lvItemsAdvancedCustomDrawItem(Sender: TCustomListView;
      Item: TListItem; State: TCustomDrawState; Stage: TCustomDrawStage;
      var DefaultDraw: Boolean);
    procedure FormShow(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure pbHeaderPaint(Sender: TObject);
    procedure acMoveDownExecute(Sender: TObject);
    procedure acMoveUpExecute(Sender: TObject);
    procedure acRemoveExecute(Sender: TObject);
    procedure acAddExecute(Sender: TObject);
    procedure lvItemsChange(Sender: TObject; Item: TListItem;
      Change: TItemChange);
    procedure tcTabsChange(Sender: TObject);
    procedure lvItemsDblClick(Sender: TObject);
    procedure lvItemsDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure lvItemsDragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure acEditExecute(Sender: TObject);
  private
    { Private declarations }
    FHeaderImages: TPngImageList;
    FContext: String;
    FSorted: Boolean;
    FModified: Boolean;
    FUpdating: Boolean;
    FOwner: TAHMUIObjectList;
    FChildren: TAHMUIObjectList;
    FMoreChildren: TAHMUIObjectList;
    function GetSelection: TAHMUIObject;
    function GetList: TAHMUIObjectList;
    procedure SetModified(Modified: Boolean);
    procedure ConfigureForm;
    procedure UpdateItem;
    procedure BuildList;
    procedure FocusItem(Focus: Integer);
    procedure FocusObject(UIObject: TAHMUIObject);
    procedure UpdateGUIState;
    procedure LoadImages;
  public
    { Public declarations }
    property ListOwner: TAHMUIObjectList read FOwner write FOwner;
    property Children: TAHMUIObjectList read FChildren write FChildren;
    property MoreChildren: TAHMUIObjectList read FMoreChildren write FMoreChildren;
    property Sorted: Boolean read FSorted write FSorted;
    property Selection: TAHMUIObject read GetSelection;
    property Modified: Boolean read FModified write SetModified;
    class function ShowList(Owner, Children, MoreChildren: TAHMUIObjectList; Sorted: Boolean = False): Boolean;
  end;


implementation

{$R *.dfm}

uses Math, ApplicationManager, Designer, BaseControls, Actions, Animators,
     Layouts, Macros, MetaValues, DesignerPropsEdit, AHMConsts;

var
  FormInstance: TfrmObjectList;

//-----------------------------------------------------------------------------
//----------------------------- Class Functions -------------------------------
//-----------------------------------------------------------------------------

class function TfrmObjectList.ShowList(Owner, Children, MoreChildren: TAHMUIObjectList; Sorted: Boolean): Boolean;
begin
  // Set default result - no changes
  Result := False;

  // Create a singleton form instance
  if not Assigned(FormInstance) then
  begin
    FormInstance := TfrmObjectList.Create(Application.MainForm);
    FormInstance.PopupParent := Application.MainForm;
  end;

  FormInstance.ListOwner := Owner;
  FormInstance.Children := Children;
  FormInstance.MoreChildren := MoreChildren;
  FormInstance.Sorted := Sorted;

  if FormInstance.ShowModal = mrOK then Result := FormInstance.Modified;
end;

//-----------------------------------------------------------------------------
//---------------------------- Property Acessors ------------------------------
//-----------------------------------------------------------------------------

function TfrmObjectList.GetSelection: TAHMUIObject;
begin
  if Assigned(lvItems.Selected) then
    Result := TAHMUIObject(lvItems.Selected.Data)
  else
    Result := nil;
end;

function TfrmObjectList.GetList: TAHMUIObjectList;
begin
  case tcTabs.TabIndex of
    0: Result := FOwner;
    1: Result := FChildren;
    else Result := FMoreChildren;
  end;
end;

procedure TfrmObjectList.SetModified(Modified: Boolean);
begin
  FModified := Modified;
  UpdateGUIState;
end;

//-----------------------------------------------------------------------------
//------------------------------- Form Events ---------------------------------
//-----------------------------------------------------------------------------

procedure TfrmObjectList.FormCreate(Sender: TObject);
begin
  // Create and populate PNG image list
  FHeaderImages := TPngImageList.Create(Self);
  LoadImages;
end;

procedure TfrmObjectList.FormShow(Sender: TObject);
begin
  ConfigureForm;
  BuildList;
  UpdateGUIState;
end;

//-----------------------------------------------------------------------------
//------------------------------ Action Events --------------------------------
//-----------------------------------------------------------------------------

procedure TfrmObjectList.acAddExecute(Sender: TObject);
var
  Index: Integer;
  NewObject: TAHMUIObject;
begin
  if tcTabs.TabIndex = 0 then
  begin
    // Use editor dialog to create a new object instance
    if TfrmObjectProperties.AddObject(NewObject, FOwner) then
    begin
      BuildList;
      FocusObject(NewObject);
      UpdateGUIState;
      Modified := True;
    end;
  end
  else
  begin
    // Reassign object from child to owner list
    FOwner.Add(GetSelection);
    Index := lvItems.Selected.Index;
    UpdateItem;
    BuildList;
    FocusItem(Index);
    UpdateGUIState;
    Modified := True;
  end;
end;

procedure TfrmObjectList.acEditExecute(Sender: TObject);
var
  UIObject: TAHMUIObject;
begin
  // Use editor dialog to modify selected object
  UIObject := GetSelection;
  if TfrmObjectProperties.EditObject(UIObject, FOwner) then
  begin
    BuildList;
    FocusObject(UIObject);
    UpdateGUIState;
    Modified := True;
  end;
end;

procedure TfrmObjectList.acMoveDownExecute(Sender: TObject);
var
  Index: Integer;
begin
  Index := lvItems.Selected.Index;
  if FOwner.MoveDown(Index) then
  begin
    UpdateItem;
    BuildList;
    FocusItem(Index + 1);
    UpdateGUIState;
    Modified := True;
  end;
end;

procedure TfrmObjectList.acMoveUpExecute(Sender: TObject);
var
  Index: Integer;
begin
  Index := lvItems.Selected.Index;
  if FOwner.MoveUp(Index) then
  begin
    UpdateItem;
    BuildList;
    FocusItem(Index - 1);
    UpdateGUIState;
    Modified := True;
  end;
end;

procedure TfrmObjectList.acRemoveExecute(Sender: TObject);
var
  Index: Integer;
begin
  Index := lvItems.Selected.Index;

  // Remove but don't free object incase it's referenced elsewhere
//TODO: if object is owned by the list then we should deprecate it here
  if FOwner.Delete(Index) then
  begin
    UpdateItem;
    BuildList;
    FocusItem(Index);
    UpdateGUIState;
    Modified := True;
  end;
end;

//-----------------------------------------------------------------------------
//----------------------------- Control Events --------------------------------
//-----------------------------------------------------------------------------

procedure TfrmObjectList.lvItemsAdvancedCustomDrawItem(Sender: TCustomListView;
  Item: TListItem; State: TCustomDrawState; Stage: TCustomDrawStage;
  var DefaultDraw: Boolean);
var
  UIObject: TAHMUIObject;
  Modified, BaseObject: Boolean;
begin
  UIObject := TAHMUIObject(Item.Data);

  if (UIObject is TAHMAction) or (UIObject is TAHMAnimation) or
     (UIObject is TAHMLayoutPosition) or (UIObject is TAHMMacroFunction) or
     (UIObject is TAHMMetaValue) then
  begin
    // These special case items don't really have a base list to own them
    if Assigned(FOwner.Owner) then Modified := FOwner.Owner.Modified
                              else Modified := False;
    BaseObject := False;
  end
  else
  begin
    Modified := UIObject.Modified;
    BaseObject := UIObject.IsBaseObject;
  end;

  // Render base items in an alternate font to match treeview on main form
  if (not (cdsFocused in State)) and BaseObject then
      Sender.Canvas.Font.Color := clActiveCaption;

  // Render modified items in italics to match treeview on main form
  if Modified then
    Sender.Canvas.Font.Style := Sender.Canvas.Font.Style + [fsItalic];
end;

procedure TfrmObjectList.lvItemsChange(Sender: TObject; Item: TListItem;
  Change: TItemChange);
begin
  // Ignore this event when exiting
  if Application.Terminated then Exit;

  if not FUpdating then UpdateGUIState;
end;

procedure TfrmObjectList.lvItemsDblClick(Sender: TObject);
begin
  if Assigned(FChildren) then
    // Remove double clicked item from list
    if tcTabs.TabIndex = 0 then acRemove.Execute
                           else acAdd.Execute
  else
    // Edit selected list item
    acEdit.Execute;
end;

procedure TfrmObjectList.lvItemsDragDrop(Sender, Source: TObject; X,
  Y: Integer);
var
  DropItem: TListItem;
  Index: Integer;
begin
  if Sender = Source then
    with TListView(Sender) do
    begin
      DropItem := GetItemAt(X, Y) ;
      if Assigned(DropItem) then Index := DropItem.Index
                            else Index := Items.Count;

      if Assigned(Selected) then
      begin
        if FOwner.Move(Selected.Index, Index) then
        begin
          UpdateItem;
          BuildList;
          FocusItem(Index);
          UpdateGUIState;
          Modified := True;
        end;
      end;
    end;
end;

procedure TfrmObjectList.lvItemsDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  Accept := (Sender = lvItems);
end;

procedure TfrmObjectList.tcTabsChange(Sender: TObject);
begin
  BuildList;
  UpdateGUIState;
end;

procedure TfrmObjectList.pbHeaderPaint(Sender: TObject);
begin
  with pbHeader do
  begin
    // Draw background
    Canvas.Brush.Color := clWhite;
    Canvas.FillRect(GetClientRect);
    // Draw appropriate header image
    FHeaderImages.Draw(Canvas, ClientWidth - FHeaderImages.Width, 0, 0);
  end;
end;

//-----------------------------------------------------------------------------
//-------------------------- User Interface Update ----------------------------
//-----------------------------------------------------------------------------

procedure TfrmObjectList.ConfigureForm;
var
  sCaption: String;
begin
  // Customise display for selected object type
  if FOwner is TAHMUIStateObjectList then
    sCaption := TAHMUIStateObjectList(FOwner).DisplayClassName
  else
    sCaption := FOwner.ClassName;
  if sCaption = '' then sCaption := 'List';
  FContext := Lowercase(sCaption);

  Caption := 'Edit ' + sCaption + ' items';
  lbCaption.Caption := 'Add or remove ' + FContext + 's';
  sCaption := 'You may add';
  if FChildren = nil then
    if FSorted then sCaption := sCaption + ', remove or edit'
               else sCaption := sCaption + ', remove, edit or re-order'
  else
    if FSorted then sCaption := sCaption + ' or remove'
               else sCaption := sCaption + ', remove or re-order';
  lbAction.Caption := sCaption + ' the ' + FContext + 's below';

  // Configure add button - will create new item if not using child list
  if FChildren = nil then acAdd.Caption := 'Add...' else acAdd.Caption := 'Add';

  // Don't show edit button if using child list
  acEdit.Visible := (FChildren = nil);

  // Don't show move up/down buttons if sorted
  acMoveUp.Visible := not FSorted;
  acMoveDown.Visible := not FSorted;

  // Hide tabs if not using a child selection list
  with tcTabs.Tabs do
  begin
    Clear;
    if Assigned(FChildren) then Add('Selected') else Add('Items');
    if Assigned(FMoreChildren) then Add(FChildren.ClassName + 's') else Add('Available');
    if Assigned(FMoreChildren) then Add(FMoreChildren.ClassName + 's');
  end;

  // Use custom column names for layout positions
  if FOwner is TAHMLayoutPositionList then lvItems.Column[0].Caption := 'Position'
                                      else lvItems.Column[0].Caption := 'Name';

  // Start focus in the list view
  lvItems.SetFocus;
end;

procedure TfrmObjectList.UpdateItem;
begin
  // Inform UIManager of change to selected item
  ApplicationManagerInstance.UIManager.InvalidateUIObject(GetSelection);
end;

procedure TfrmObjectList.BuildList;
  procedure AddItem(Item: TAHMUIObject);
  begin
    with lvItems.Items.Add do
    begin
      Caption := Item.Name;
      SubItems.Add(Item.DesignerInfo);
      if Item is TAHMControl then
        ImageIndex := Item.ImageIndex + STATE_IMG_OBJECTS
      else
        ImageIndex := Item.ImageIndex + STATE_IMG_ACTIONS;
      Data := Pointer(Item);
    end;
  end;
var
  i: Integer;
begin
  FUpdating := True;
  lvItems.Items.BeginUpdate;
  lvItems.Items.Clear;
  try
    // Add owned items or unassigned child items to listview
    for i := 0 to Pred(GetList.TotalCount) do
      if (GetList = FOwner) or not FOwner.Contains(GetList.Objects[i]) then
        AddItem(GetList[i]);
  finally
    lvItems.Items.EndUpdate;
    FUpdating := False;
  end;
end;

procedure TfrmObjectList.FocusItem(Focus: Integer);
begin
  Focus := Min(Focus, lvItems.Items.Count - 1);
  if Focus >= 0 then
  begin
    lvItems.Items[Focus].Selected := True;
    lvItems.Items[Focus].MakeVisible(False);
  end;
end;

procedure TfrmObjectList.FocusObject(UIObject: TAHMUIObject);
var
  i: Integer;
begin
  for i := 0 to Pred(lvItems.Items.Count) do
    if lvItems.Items[i].Data = UIObject then
    begin
      FocusItem(i);
      Break;
    end;
end;

procedure TfrmObjectList.UpdateGUIState;
var
  Idle: Boolean;
begin
  Idle := (tcTabs.TabIndex = 0);

  with lvItems do
  begin
    acAdd.Enabled := (FChildren = nil) or ((not Idle) and Assigned(Selected));
    acEdit.Enabled := Idle and Assigned(Selected);
    acRemove.Enabled := Idle and Assigned(Selected);
    acMoveUp.Enabled := Idle and Assigned(Selected) and (Selected.Index > 0);
    acMoveDown.Enabled := Idle and Assigned(Selected) and (Selected.Index < Pred(Items.Count));

    if Idle then DragMode := dmAutomatic else DragMode := dmManual;
  end;
end;

procedure TfrmObjectList.LoadImages;
begin
  with FHeaderImages do
  begin
    Clear;
    Width := 150; Height := 57;

    AddResourcePng('HD_IMG_GENERIC'); //0
  end;

  // Use image lists from main designer form treeview
  with Application.MainForm as TAHMDesignerForm do
  begin
    Self.lvItems.LargeImages := LargeTreeImages;
    Self.lvItems.SmallImages := SmallTreeImages;
  end;
end;

end.
